﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace LetiLib.FontData
{
    public class Document
    {
        [XmlElement("Title")]
        public string Title { get; set; }

        [XmlElement("Width")]
        public int Width { get; set; }

        [XmlElement("Height")]
        public int Height { get; set; }

        [XmlElement("Element", typeof(Element))]
        public List<Element> Elements { get; set; }

        private Point[,] pointArray = new Point[400, 300];

        [XmlIgnore()]
        public Point[,] PointArray { get { return pointArray; } set { pointArray = value; } }

        //[XmlIgnore()]
        //private List<Point> map;

        //[XmlIgnore()]
        //public List<Point> Map
        //{
        //    get
        //    {
        //        Draw();
        //        return map;
        //    }
        //}

        public Document()
        {
            Elements = new List<Element>();
            Width = 144;
            Height = 96;
            Title = "Untitled";
        }

        public Document(int width, int height, string title = "Untitled")
        {
            Elements = new List<Element>();
            Width = width;
            Height = height;
            Title = title;
        }

        public void Draw()
        {
            foreach (Element e in Elements)
            {
                foreach (Point p in e.Map)
                {
                    int x = p.X + e.Left;
                    int y = p.Y + e.Top;
                    if (pointArray[x, y] == null)
                    {
                        pointArray[x, y] = p;
                    }
                    else
                    {
                        if (p.Owner != null)
                        {
                            if (p.Owner.Clickable)
                            {
                                pointArray[x, y] = p;
                            }
                        }
                    }
                }
            }
            //map = new List<Point>();
            //foreach (Element e in Elements)
            //{

            //    foreach (Point p in e.Map)
            //    {
            //        map.Add(new Point(p.Owner, p.X + e.Top, p.Y + e.Left, p.State));
            //    }
            //}
        }

        public void Clicked(int x, int y)
        {
            //foreach (Point p in map)
            //{
            //    Element e;
            //    if (p.X == x && p.Y == y && p.Owner!=null)
            //    {
            //        p.Owner.ChangeState();
            //        break;
            //    }
            //}
            if (pointArray[x, y] != null)
            {
                if (pointArray[x, y].Owner != null)
                {
                    pointArray[x, y].Owner.ChangeState();
                    Draw();
                }
            }
        }
    }

    [XmlInclude(typeof(TextLineElement))]
    [XmlInclude(typeof(LineElement))]
    [XmlInclude(typeof(CustomElement))]
    public abstract class Element
    {
        public enum Color
        {
            Black = 0,
            Green = 1,
            Red = 2,
            Yellow = 3
        }

        [XmlElement("Top")]
        public int Top { get; set; }

        [XmlElement("Left")]
        public int Left { get; set; }

        //State
        // - 0: Black
        // - 1: Green
        // - 2: Red
        // - 3: Yellow
        [XmlElement("State")]
        public int State { get; set; }

        [XmlElement("Clickable")]
        public Boolean Clickable { get; set; }

        [XmlIgnore()]
        public List<Point> map;

        [XmlIgnore()]
        public List<Point> Map
        {
            get
            {
                if (map == null) map = new List<Point>();
                Draw();
                return map;
            }

            set
            {
                map = value;
            }
        }

        public Element()
        {
            Top = 0;
            Left = 0;
            State = 1;
            Clickable = true;
        }

        public void ChangeState()
        {
            switch (State)
            {
                case 0: State = 0; break;
                case 1: State = 2; break;
                case 2: State = 3; break;
                case 3: State = 1; break;
            }
        }

        public abstract void Draw();
    }

    public class LineElement : Element
    {
        // Size
        // 1 - Small  - 3px
        // 2 - Medium - 5px
        // 3 - Large  - 7px
        [XmlElement("Size")]
        public int Size { get; set; }

        [XmlElement("Length")]
        public int Length { get; set; }

        // Direction
        // 0 - 00h00
        // 1 - 01h30
        // 2 - 03h00
        // 3 - 04h30
        // 4 - 06h00
        // 5 - 07h30
        // 6 - 09h00
        // 7 - 10h30
        [XmlElement("Direction")]
        public int Direction { get; set; }

        [XmlElement("HasArrow")]
        public bool HasArrow { get; set; }

        public LineElement()
            : this(0, 0, 1, 10, 0) { }

        public LineElement(int top, int left, int size, int length, int direction)
            : this(top, left, size, length, direction, false) { }

        public LineElement(int top, int left, int size, int length, int direction, bool hasArrow)
            : base()
        {
            Top = top;
            Left = left;
            Direction = direction;
            Length = length;
            Size = size;
            Clickable = true;
            HasArrow = hasArrow;
        }

        public override void Draw()
        {
            int size = 5;

            switch (Size)
            {
                case 1: size = 3; break;
                case 2: size = 5; break;
                case 3: size = 7; break;
            }
            switch (Direction)
            {
                case 0:
                    for (int i = 0; i < size; i++)
                    {
                        for (int j = 0; j < Length; j++)
                        {
                            int x = i;
                            int y = j;
                            map.Add(new Point(this, x, y, State));
                        }
                    }

                    if (HasArrow)
                    {
                        int seed = (size / 2 + 1);
                        //add base line of arrow
                        for (int i = 0; i < (size + seed * 2); i++)
                        {
                            map.Add(new Point(this, i - seed, -1, State));
                        }

                        for (int i = 1; i <= (size / 2) + seed; i++)
                        {
                            for (int j = i; j < (size + seed * 2) - i; j++)
                            {
                                map.Add(new Point(this, j - seed, -(i * 2 + 1), State));
                                map.Add(new Point(this, j - seed, -(i * 2), State));
                            }
                        }
                    }
                    break;
                case 1:
                    size += 2;
                    for (int i = 0; i < size; i++)
                    {
                        for (int j = 0; j < Length; j++)
                        {
                            int x = i + j - (i / 2);
                            int y = j + size / 2 - (i / 2);
                            map.Add(new Point(this, x, y, State));
                        }
                    }


                    break;
                case 2:
                    for (int i = 0; i < size; i++)
                    {
                        for (int j = 0; j < Length; j++)
                        {
                            int x = j;
                            int y = i;
                            map.Add(new Point(this, x, y, State));
                        }
                    }
                    if (HasArrow)
                    {
                        int seed = (size / 2 + 1);
                        //add base line of arrow
                        for (int i = 0; i < (size + seed * 2); i++)
                        {
                            map.Add(new Point(this, Length, i - seed, State));
                        }

                        for (int i = 1; i <= (size / 2) + seed; i++)
                        {
                            for (int j = i; j < (size + seed * 2) - i; j++)
                            {
                                map.Add(new Point(this, Length + i * 2 - 1, j - seed, State));
                                map.Add(new Point(this, Length + i * 2, j - seed, State));
                            }
                        }

                    }
                    break;
                case 3:
                    size += 2;
                    for (int i = 0; i < size; i++)
                    {
                        for (int j = 0; j < Length; j++)
                        {
                            int y = i + j - (i / 2);
                            int x = j + size / 2 - (i / 2);
                            map.Add(new Point(this, x, y, State));
                        }
                    }
                    if (HasArrow)
                    {
                        int x = size / 2 + Length;
                        int y = size / 2 + Length;
                        map.Add(new Point(this, x, y, State));

                        map.Add(new Point(this, x - 1, y, State));
                        map.Add(new Point(this, x, y - 1, State));

                        map.Add(new Point(this, x - 1, y - 1, State));
                        map.Add(new Point(this, x - 2, y, State));
                        map.Add(new Point(this, x, y - 2, State));

                        map.Add(new Point(this, x - 2, y - 2, State));
                        map.Add(new Point(this, x - 3, y - 1, State));
                        map.Add(new Point(this, x - 1, y - 3, State));
                        map.Add(new Point(this, x - 2, y, State));
                        map.Add(new Point(this, x, y - 2, State));
                        //    for (int i = 0; i <= 5; i++)
                        //    {
                        //        for (int j = 1; j <= i; j++)
                        //        {
                        //            int x = (size + Length - 1) - (size + 1) + i - 2;
                        //            int y = (size + Length - 1) - j - 1;
                        //            map.Add(new Point(this, x, y, State));
                        //        }
                        //    }
                    }
                    break;
                case 4:
                    for (int i = 0; i < size; i++)
                    {
                        for (int j = 0; j < Length; j++)
                        {
                            int x = i;
                            int y = j;
                            map.Add(new Point(this, x, y, State));
                        }
                    }
                    if (HasArrow)
                    {
                        int seed = (size / 2 + 1);
                        //add base line of arrow
                        for (int i = 0; i < (size + seed * 2); i++)
                        {
                            map.Add(new Point(this, i - seed, Length, State));
                        }

                        for (int i = 1; i <= (size / 2) + seed; i++)
                        {
                            for (int j = i; j < (size + seed * 2) - i; j++)
                            {
                                map.Add(new Point(this, j - seed, Length + i * 2 - 1, State));
                                map.Add(new Point(this, j - seed, Length + i * 2, State));
                            }
                        }

                    }
                    break;
                case 5:
                    size += 2;
                    for (int i = 0; i < size; i++)
                    {
                        for (int j = 0; j < Length; j++)
                        {
                            int x = i + j - (i / 2);
                            int y = j + size / 2 - (i / 2);
                            map.Add(new Point(this, x, y, State));
                        }
                    }
                    break;
                case 6:

                    for (int i = 0; i < size; i++)
                    {
                        for (int j = 0; j < Length; j++)
                        {
                            int x = j;
                            int y = i;
                            map.Add(new Point(this, x, y, State));
                        }
                    }

                    if (HasArrow)
                    {
                        int seed = (size / 2 + 1);
                        //add base line of arrow
                        for (int i = 0; i < (size + seed * 2); i++)
                        {
                            map.Add(new Point(this, -1, i - seed, State));
                        }

                        for (int i = 1; i <= (size / 2) + seed; i++)
                        {
                            for (int j = i; j < (size + seed * 2) - i; j++)
                            {
                                map.Add(new Point(this, -(i * 2 + 1), j - seed, State));
                                map.Add(new Point(this, -(i * 2), j - seed, State));
                            }
                        }

                    }
                    break;
                case 7:
                    size += 2;
                    for (int i = 0; i < size; i++)
                    {
                        for (int j = 0; j < Length; j++)
                        {
                            int y = i + j - (i / 2);
                            int x = j + size / 2 - (i / 2);
                            map.Add(new Point(this, x, y, State));
                        }
                    }
                    break;
            }
        }
    }

    public class TextLineElement : Element
    {
        [XmlElement("Text")]
        public string Text { get; set; }

        [XmlElement("Align")]
        public string Align { get; set; }

        [XmlElement("FontSize")]
        public byte FontSize { get; set; }

        [XmlIgnore]
        public int Width { get; set; }

        public TextLineElement()
            : this(0, 0, "", 1)
        {
        }

        public TextLineElement(int top, int left, string text, int state, byte size = 8)
        {
            Top = top;
            Left = left;
            FontSize = GetTextSize(size, text);
            Text = text;
            State = state;

            //Utils.Instance().LoadCharsMap();
        }

        public override void Draw()
        {
            Text = ProcessingText(Text);
            int currentX = 0;
            CustomElement e = new CustomElement();
            foreach (char c in Text)
            {
                try
                {
                    e = ElementsMap.Instance().GetElement(c.ToString(), FontSize);

                    foreach (Point p in e.Mask)
                    {
                        map.Add(new Point((Clickable == true) ? this : null, p.X + currentX, p.Y, State));
                    }
                    currentX += e.Width;
                }
                catch { }
            }

            Width = currentX;
            //if (Left == 0)
            //    RecalLeftPos();
        }

        private void RecalLeftPos()
        {
            int newLeftPos = 0;
            if (Width < 144)
            {
                newLeftPos = (144 - Width) / 2;
            }

            Left = newLeftPos;
        }

        protected byte GetTextSize(byte size, string text)
        {
            return (byte)((text.Count(p => '+'.Equals(p)) * 2 + size) > 12 ? 12 : (text.Count(p => '+'.Equals(p)) * 2 + size));
        }

        protected string ProcessingText(string text)
        {
            text = text.Replace("+", "");
            text = text.Replace("%DATE%", System.DateTime.Now.ToString("dd-MM-yyyy"));
            text = text.Replace("%TIME%", System.DateTime.Now.ToString("HH:mm"));
            return text;
        }
    }

    public class CustomElement : Element
    {
        [XmlElement("PointMap")]
        public string PointMap { get; set; }
        [XmlIgnore]
        public List<Point> Mask { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }

        public CustomElement()
            : base()
        {
            Width = 9;
            Height = 14;
            Mask = new List<Point>();
        }

        public CustomElement(int height, int width, string pointMap)
            : this()
        {
            Width = width;
            Height = height;
            PointMap = pointMap;
            //Mask = GetPointList().ToList<Point>();
        }

        private IEnumerable<Point> GetPointList()
        {
            if (!string.IsNullOrEmpty(PointMap))
            {
                string[] addresses = PointMap.Split(',');
                for (int i = 0; i < addresses.Length; i = i + 2)
                    yield return new Point(addresses[i].GetByteValue(), addresses[i + 1].GetByteValue());
            }
        }

        public override void Draw()
        {
            Mask = GetPointList().ToList<Point>();
            foreach (Point p in Mask)
            {
                map.Add(new Point((Clickable == true) ? this : null, p.X, p.Y, State));
            }
        }
    }

    public class ShapeElement : Element
    {
        [XmlElement("Type")]
        public int Type { get; set; }

        public List<Point> Mask { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }

        public override void Draw()
        {
            throw new NotImplementedException();
        }
    }

    public class Point
    {
        [XmlElement("X")]
        public int X { get; set; }

        [XmlElement("Y")]
        public int Y { get; set; }

        [XmlElement("State")]
        public int State { get; set; }

        [XmlIgnore()]
        public Element Owner { get; set; }

        [XmlIgnore()]
        public Color Color
        {
            get
            {
                switch (State)
                {
                    case 0: return Color.Black;
                    case 1: return Color.Lime;
                    case 2: return Color.Red;
                    case 3: return Color.Yellow;
                    default: return Color.Black;
                }
            }
        }


        public Point() : this(10, 10) { }

        public Point(int x, int y, int state = 1)
            : base()
        {
            X = x; Y = y; State = state;
        }

        public Point(Element owner, int x, int y, int state)
            : this(x, y, state)
        {
            if (owner != null)
            {
                if (owner.Clickable) Owner = owner;
            }
        }
    }
}
